<!--
 * @Description:轻量级流程图编辑器,这是一个基础组件详见cBpmn写法
 * @Version: 2.0
 * @Autor: wiz
 * @Date: 2023-06-27 14:23:35
 * @LastEditors: zss
 * @LastEditTime: 2024-11-12 16:40:29
-->
<template>
  <div id="mountNode">
    <div class="mask-container" v-if="isMask"></div>
    <div :class="['editor', isFullScreen ? 'fullScreen' : '']">
      <div class="main">
        <div v-show="showToolbar" class="top">
          <o-simple-toolbar :list="toolbarList" @click="toolbarClick" />
        </div>
        <div :id="bodyID" class="body">
          <div v-show="showStencil" :id="stencilContainer" class="left"></div>
          <div :id="graphContainer" class="garaph-container"></div>
          <div v-show="showMinimap" class="minmap" :id="minimapContainer"></div>
          <o-card-box
            position="right"
            :width="rightPanelWidth"
            :isExpand="rightPanelOpened"
            v-show="showRightPanel"
          >
            <slot name="rightPanel"></slot>
          </o-card-box>
          <oContextMenu
            v-model="showContextMenu"
            :compBoxInfo="GT_compBoxInfo"
            :list="contextMenuList"
            :displayPosition="showPosition"
            :width="contextMunuWidth"
            @on-select="onContextSelect"
          ></oContextMenu>
        </div>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import {
  Clipboard,
  custBaseReg,
  defaultContextMenuItem,
  defaultEvent,
  Dnd,
  Export,
  History,
  I_cmd,
  ifs,
  Keyboard,
  MiniMap,
  N_cFlowchart,
  N_oContextMenu,
  N_oSimpleToolbar,
  regVueNode,
  Selection,
  Snapline,
  Stencil,
  T_cmd,
  T_srv,
  toolbarConf,
  Transform,
  Tsrv,
  vue
} from './config';

@T_srv.comp
export default class cFlowchart extends vue {
  /* 父组件compBoxInfo */
  @T_srv.prop({
    required: false,
    type: Object,
    default: null
  })
  compBoxInfo: ifs.I_compBoxInfo;

  @T_srv.prop({
    required: false,
    type: Object,
    default: null
  })
  config: N_cFlowchart.I_conf;

  /* 组件名 */
  public name = 'cFlowchart';

  private G_compName: string = this.name;

  /* 组件箱信息对象 */
  private GT_compBoxInfo: ifs.I_compBoxInfo;

  /* 组件箱对象 */
  private GT_compBox: ifs.I_compBox;

  /* 组件箱识别ID */
  private G_compID: string;

  private GT_cmd: I_cmd = new T_cmd();

  public graphContainer: string = Tsrv.utils.custUUID(8);

  public minimapContainer: string = Tsrv.utils.custUUID(8);

  public stencilContainer: string = Tsrv.utils.custUUID(8);

  private graph: N_cFlowchart.x6Graph;

  public isMask: boolean = false;

  public showMinimap: boolean = false;

  public showToolbar: boolean = false;

  public showStencil: boolean = false;

  private isFullScreen: boolean = false;

  public toolbarList: Array<N_oSimpleToolbar.I_Conf> = toolbarConf();

  public contextMenuList: Array<N_oContextMenu.I_menuItem> = [];

  public showContextMenu: boolean = false;

  public showPosition: N_oContextMenu.I_showPosition = null;

  public bodyID = Tsrv.utils.custUUID(8);

  private bodyContainer: HTMLElement = null;

  private contextMunuWidth: number = 150;

  public showRightPanel: boolean = false;

  public rightPanelOpened: boolean = false;

  public rightPanelWidth: number = 200;

  public rightPanelHeight: number = null;

  created() {
    // 如果父组件没有传参，那就产生一个
    this.GT_compBoxInfo = Tsrv.getCompInfo(this.compBoxInfo);
    this.G_compID = this.GT_compBoxInfo.compID;
    this.GT_compBox = this.GT_compBoxInfo.TcompBox;
    this.GT_cmd.init(this, this.GT_compBoxInfo, this.graphContainer);
    this.init();
  }

  public init() {
    if (!Tsrv.noValue(this.config?.toolbar?.list)) {
      // 替换默认的按钮
      this.toolbarList.length = 0; // 清空默认
      if (typeof this.config.toolbar.list === 'function') {
        const list = this.config.toolbar.list();
        if (!Tsrv.noValue(list)) {
          this.toolbarList.push(...list);
        }
      } else if (Array.isArray(this.config.toolbar.list)) {
        this.toolbarList.push(...this.config.toolbar.list);
      }
    }
    this.GT_cmd.setStdBtnList(this.toolbarList);

    custBaseReg.initReg();

    if (!Tsrv.noValue(this.config?.garph?.minimap)) {
      this.showMinimap = this.config.garph.minimap;
    }
    if (!Tsrv.noValue(this.config?.toolbar?.show)) {
      this.showToolbar = this.config.toolbar.show;
    }
    if (!Tsrv.noValue(this.config?.stencil?.show)) {
      this.showStencil = this.config.stencil.show;
    }
    if (!Tsrv.noValue(this.config?.contextMenu?.width)) {
      this.contextMunuWidth = this.config.contextMenu.width;
    }
    if (!Tsrv.noValue(this.config?.rightPanel)) {
      this.showRightPanel = this.config.rightPanel.show;
      this.rightPanelOpened = this.config.rightPanel.opened;
      this.rightPanelWidth = this.config.rightPanel.width;
      if (this.config.rightPanel?.height) {
        this.rightPanelHeight = this.config.rightPanel.height;
      }
    }
    // 设置语言
    this.$i18n.locale = Tsrv.getSysDefLang();

    this.register();
    this.initDisplay();
  }

  private initDisplay() {
    if (!Tsrv.noValue(this.config?.workMode)) {
      this.GT_cmd.workMode = this.config.workMode;
    }
  }

  private initDefaultContextMenuItem() {
    // 非编辑状态不可删除
    if (this.GT_cmd.isDenyEdit()) {
      this.contextMenuList = defaultContextMenuItem().filter(
        el => el.name !== 'default:remove'
      );
      return;
    }
    this.contextMenuList = defaultContextMenuItem();
  }

  public updRegister() {
    this.register('UPD');
    this.regGraphListener();
    this.initDisplay();
  }

  private register(s: 'ALL' | 'UPD' = 'ALL') {
    if (s === 'ALL') {
      // 注册节点
      if (!Tsrv.noValue(this.config?.regNodes)) {
        let nodes: N_cFlowchart.I_node[] = [];
        if (typeof this.config.regNodes === 'function') {
          nodes = this.config.regNodes();
        } else if (Array.isArray(this.config.regNodes)) {
          nodes = this.config.regNodes;
        }

        nodes.forEach(el => {
          N_cFlowchart.x6Graph.registerNode(el.name, el.cls, el.overwrite);
          this.GT_cmd.recRegNode(el);
        });
      }
      // 注册 vue node
      if (!Tsrv.noValue(this.config?.regVueNodes)) {
        let nodes: N_cFlowchart.I_vueNode[] = [];
        if (typeof this.config.regVueNodes === 'function') {
          nodes = this.config.regVueNodes();
        } else if (Array.isArray(this.config.regVueNodes)) {
          nodes = this.config.regVueNodes;
        }
        nodes.forEach(el => {
          regVueNode(el);
        });
      }
      // 注册边
      if (!Tsrv.noValue(this.config?.regEdges)) {
        let edges: N_cFlowchart.I_edge[] = [];
        if (typeof this.config.regEdges === 'function') {
          edges = this.config.regEdges();
        } else if (Array.isArray(this.config.regNodes)) {
          edges = this.config.regEdges;
        }
        edges.forEach(el => {
          N_cFlowchart.x6Graph.registerEdge(el.name, el.options, el.overwrite);
          this.GT_cmd.recRegEdge(el);
        });
      }
    }
    // 注册工具栏按扭
    this.GT_cmd.setStdBtnList(this.toolbarList);
    if (!Tsrv.noValue(this.config?.regToolbar)) {
      if (typeof this.config.regToolbar === 'function') {
        const list = this.config.regToolbar();
        if (!Tsrv.noValue(list)) {
          this.toolbarList.push(...list);
        }
      } else if (Array.isArray(this.config.regToolbar)) {
        this.toolbarList.push(...this.config.regToolbar);
      }
    }
    // 注册工具栏事件
    if (!Tsrv.noValue(this.config?.regToolbarEvent)) {
      this.GT_cmd.cleanToolbarEvent();
      if (this.config.regToolbarEvent instanceof Map) {
        this.config.regToolbarEvent.forEach((value, key) => {
          this.GT_cmd.regToolbarEvent(key, value);
        });
      }
    }
    // 注册layout
    if (!Tsrv.noValue(this.config?.layout)) {
      this.GT_cmd.setLayoutObj(this.config.layout);
    }
  }

  mounted() {
    this.bodyContainer = document.getElementById(this.bodyID);
    this.config.garph.container = document.getElementById(this.graphContainer);
    // eslint-disable-next-line new-cap
    this.graph = new N_cFlowchart.x6Graph(this.config.garph);
    this.GT_cmd.setGarph(this.graph, this.config.garph);
    this.graphPlugin();
    this.defEvent();
    this.regGraphListener();
    this.GT_compBox.addCompMergeName(this.G_compID, this.G_compName, this);
    this.$emit('compBoxInfo', this.GT_compBoxInfo);
    this.initDefaultContextMenuItem();
    this.GT_cmd.graphEditorState = 'none';
    // 创建快捷功能
    this.createShortcuts();
    this.addCellValidate();
    // 注册快捷方式
    this.regShortcuts();
  }

  beforedestroy() {
    this.graph.dispose();
  }

  private graphPlugin() {
    this.setSnapline(); // 对齐线
    this.setMinMap();
    this.setHistory();
    this.setClipboard();
    this.setTransform();
    this.setDnd();
    this.setExport();
    this.setScroller();
    this.setClipboard();
    this.setKeyboard();
    this.setSelection();
    this.setScroller();
    this.setStencilContainer();
  }

  // 调整节点尺寸和角度
  private setTransform() {
    this.GT_cmd.Ttransform = new Transform({
      resizing: {
        enabled: true
      },
      rotating: {
        enabled: true,
        grid: 1
      }
    });
    this.graph.use(this.GT_cmd.Ttransform);
  }

  // 剪贴板
  private setClipboard() {
    this.GT_cmd.Tclipboard = new Clipboard({
      enabled: true
    });
    this.graph.use(this.GT_cmd.Tclipboard);
  }

  // 快捷 键
  private setKeyboard() {
    this.GT_cmd.Tkeyboard = new Keyboard({
      enabled: true,
      global: false
    });
    this.graph.use(this.GT_cmd.Tkeyboard);
  }

  // 画布内容通过图片的形式导出
  private setExport() {
    this.GT_cmd.Texport = new Export();
    this.graph.use(this.GT_cmd.Texport);
  }

  // 滚动画布插件
  private setScroller() {
    // 当同时使用 Scroller 插件的时候请不要同时开启画布的
    // panning 配置，因为两种形式在交互上有冲突。
    // this.GT_cmd.Tscroller = new Scroller({
    //   enabled: true
    // });
    // this.graph.use(this.GT_cmd.Tscroller);
  }

  // 通过拖拽交互往画布中添加节点
  private setDnd() {
    this.GT_cmd.Tdnd = new Dnd({
      target: this.graph
      // validateNode: (node) => false,
    });
    this.graph.use(this.GT_cmd.Tdnd);
  }

  private setSnapline() {
    if (!Tsrv.noValue(this.config.garph?.snapline)) {
      if (this.config.garph.snapline === true) {
        this.GT_cmd.Tsnapline = new Snapline({
          enabled: true
        });
        this.graph.use(this.GT_cmd.Tsnapline);
      }
    }
  }

  private setHistory() {
    if (!Tsrv.noValue(this.config.garph?.history)) {
      if (this.config.garph.history === true) {
        this.GT_cmd.Thistory = new History({
          enabled: true
        });
        this.graph.use(this.GT_cmd.Thistory);
        this.graph.on('history:change', () => {
          this.GT_cmd.setHistoryStatus(
            this.graph.canUndo(),
            this.graph.canRedo()
          );
        });
      }
    }
  }

  private setMinMap() {
    if (this.config.garph.minimap) {
      this.GT_cmd.TminiMap = new MiniMap({
        container: document.getElementById(this.minimapContainer),
        graphOptions: {
          connecting: this.config.garph.connecting
        }
      });
      this.graph.use(this.GT_cmd.TminiMap);
    }
  }

  private setSelection() {
    if (this.config.selection) {
      this.GT_cmd.Tselection = new Selection(this.config.selection);
      this.graph.use(this.GT_cmd.Tselection);
    }
  }

  private regGraphListener() {
    this.defEvent(); // 默认监听事件，如何有同名注册事件则被覆盖
    // 监听注册事件
    if (!Tsrv.noValue(this.config?.regEvent)) {
      if (this.config.regEvent instanceof Map) {
        this.config.regEvent.forEach((fn, key) => {
          this.graph.off(key);
          this.graph.on(key, e => {
            fn(e);
            this.GT_cmd.afterGraphOn(key, e); // 所有事件钩子
          });
        });
      }
    }
  }

  private setStencilContainer() {
    // {
    //   target: this.graph,
    //   title: Tsrv.getI18nByValue('节点'),
    //   search: false,
    //   placeholder: Tsrv.getI18nByValue('搜索节点'),
    //   notFoundText: Tsrv.getI18nByValue('未找到'),
    //   stencilGraphHeight: 0,
    //   collapsable: true,
    //   groups: [
    //     {
    //       name: Tsrv.getI18nByValue('流程图')
    //     }
    //   ]
    // }
    const stencilOpts = this.config.stencil.options;
    stencilOpts.target = this.graph;
    if (!Tsrv.noValue(stencilOpts?.groups)) {
      stencilOpts.groups.forEach(el => {
        el.name = Tsrv.getI18nByValue(el.name);
      });
    }
    if (!Tsrv.noValue(stencilOpts?.title)) {
      stencilOpts.title = Tsrv.getI18nByValue(stencilOpts.title);
    }
    if (!Tsrv.noValue(stencilOpts?.placeholder)) {
      stencilOpts.placeholder = Tsrv.getI18nByValue(stencilOpts.placeholder);
    }
    if (!Tsrv.noValue(stencilOpts?.notFoundText)) {
      stencilOpts.notFoundText = Tsrv.getI18nByValue(stencilOpts.notFoundText);
    }
    const stencil = new Stencil(stencilOpts);
    this.GT_cmd.Tstencil = stencil;
    this.GT_cmd.getRegNode().forEach(cf => {
      const w = Tsrv.getx(cf, 'cls.attrs.body.width', 80);
      const h = Tsrv.getx(cf, 'cls.attrs.body.height', 40);
      const n = this.graph.createNode({
        shape: cf.name,
        width: w,
        height: h
      });
      this.GT_cmd.setItemNodes([n.clone()]);
    });
    document
      .getElementById(this.stencilContainer)
      .appendChild(stencil.container);
    stencil.load(this.GT_cmd.getItemNodes(), Tsrv.getI18nByValue('流程图'));
  }

  /**
   * @description: 添加cell（节点/边）校验
   * @return {*}
   * @author: swc
   * @Date: 2024-08-17 09:40:31
   * @LastEditors: swc
   * @LastEditTime: Do not edit
   */
  private addCellValidate() {
    this.graph.on('node:added', ({ node, index, options }) => {
      const data = node.getData();
      if (!Tsrv.noValue(data)) {
        if (Tsrv.utils.get(data, 'unique', false)) {
          if (
            this.graph.getNodes().filter(el => el.shape === node.shape).length >
            1
          ) {
            this.graph.removeNode(node.id);
          }
        }
      }
    });
  }

  /**
   * @description: 创建快捷功能——例如：backspace键删除选中cell
   * @return {*}
   * @author: swc
   * @Date: 2024-08-17 09:42:18
   * @LastEditors: swc
   * @LastEditTime: Do not edit
   */
  private createShortcuts() {
    // backspace键——删除cell
    this.graph.bindKey(
      'backspace',
      () => {
        if (!this.graph.isSelectionEmpty()) {
          const cells = this.graph.getSelectedCells();
          this.graph.removeCells(cells);
        }
        return false;
      },
      'keyup'
    );
  }

  private regShortcuts() {
    // 注册快捷功能
    if (!Tsrv.noValue(this.config?.regShortcuts)) {
      this.GT_cmd.cleanShortcuts();
      if (this.config.regShortcuts instanceof Map) {
        this.config.regShortcuts.forEach((value, key) => {
          this.GT_cmd.regShortcuts(key, value);
        });
      }
    }
    // 创建快捷功能
    if (this.GT_cmd.getShortcuts().size > 0) {
      this.GT_cmd.getShortcuts().forEach((value, key) => {
        this.graph.bindKey(key, this.GT_cmd.execShortcuts(key), 'keyup');
      });
    }
  }

  /**
   * @description: 销毁
   * @return {*}
   * @author: swc
   * @Date: 2024-08-17 09:43:22
   * @LastEditors: swc
   * @LastEditTime: Do not edit
   */
  private destructionTask() {
    // 销毁 快捷功能
    this.graph.clearKeys();
  }

  destroyed() {
    /* 注册组件销毁 */
    this.GT_compBox.removeCompMergeName(this.G_compID, this.G_compName);

    /* 如果父组件传入的compBoxInfo则删除本文件产生的引用,反之是本组件产生的ID统一删除 */
    if (Tsrv.utils.isEmpty(this.compBoxInfo)) {
      this.GT_compBox.removeCompsWithCompID(this.G_compID);
    } else {
      this.GT_compBox.removeCompsMergeName(this.G_compID, [
        `'${this.G_compName}'`
      ]);
    }
    this.destructionTask();
  }

  public loadGraphData(cell: N_cFlowchart.x6Cell[]) {
    this.graph.resetCells(cell);
  }

  // 缩放
  public zoomToFit(
    opt: any = {
      padding: 10,
      preserveAspectRatio: true
    }
  ) {
    this.graph.zoomToFit(opt);
  }

  public createEdge(item: N_cFlowchart.I_item): N_cFlowchart.x6Edge {
    return this.graph.createEdge(item);
  }

  public createNode(item: N_cFlowchart.I_item): N_cFlowchart.x6Node {
    return this.graph.createNode(item);
  }

  public getCells(): N_cFlowchart.x6Cell[] {
    return this.graph.getCells();
  }

  public getNodes(): N_cFlowchart.x6Node[] {
    return this.graph.getNodes();
  }

  public getEdges(): N_cFlowchart.x6Edge[] {
    return this.graph.getEdges();
  }

  public getLayoutObj(): any {
    return this.GT_cmd.getLayoutObj();
  }

  private defEvent() {
    if (!Tsrv.noValue(defaultEvent())) {
      defaultEvent().forEach(el => {
        this.graph.on(el.toString(), e => {
          this.GT_cmd.afterGraphOn(el, e);
        });
      });
    }
  }

  public toolbarClick(cf: N_oSimpleToolbar.I_Conf, e: any) {
    this.GT_cmd.execEvent(`before_${cf.key}`)
      .then(bok => {
        if (bok) {
          this.GT_cmd.execEvent(cf.key).then(ok => {
            if (ok) {
              this.GT_cmd.execEvent(`after_${cf.key}`)
                .then(aok => {
                  if (aok) {
                    this.GT_cmd.toolbarClickHandle(cf, e);
                  }
                })
                .catch(er => {
                  throw new Error(er);
                });
            }
          });
        }
      })
      .catch(er => {
        throw new Error(er);
      });
  }

  public onContextSelect(key: string) {
    if (!Tsrv.noValue(defaultContextMenuItem().find(el => el.name === key))) {
      this.GT_cmd.defContentMenuClick(key);
    }
    this.$emit('contextmenu-select', key, this.GT_cmd);
  }

  public displayContextMenu(
    pos: N_oContextMenu.I_showPosition,
    contextMenuItems: N_oContextMenu.I_menuItem[],
    e: N_cFlowchart.I_eventParams
  ) {
    if (
      this.GT_cmd.graphEditorState === 'none' ||
      this.GT_cmd.workMode === 'view'
    ) {
      return;
    }
    const { top, left } = Tsrv.utils.getElementDistanceFromScreenEdges(
      this.bodyContainer
    );
    pos.clientX -= left;
    pos.clientY -= top;
    this.showPosition = pos;
    if (!Tsrv.noValues(pos, ...contextMenuItems)) {
      this.showPosition = pos;
      // this.initDefaultContextMenuItem();
      // 判断语句换成 正则判断
      if (
        contextMenuItems.some(
          el =>
            el.name.indexOf('blank:') > -1 ||
            el.name.indexOf('graph:') > -1 ||
            el.name.indexOf('custom:') > -1
        )
      ) {
        this.contextMenuList = [];
      } else {
        this.initDefaultContextMenuItem();
      }
      this.contextMenuList.push(...contextMenuItems);
      if (this.contextMenuList.length > 0) {
        this.showContextMenu = true;
      }
    }
  }

  public hideContextMenu() {
    this.showContextMenu = false;
  }

  public addNode(p: N_cFlowchart.x6NodeMetadata) {
    this.graph.addNode(p);
  }

  public addEdge(p: N_cFlowchart.x6EdgeMetadata) {
    this.graph.addEdge(p);
  }

  public removeNode(id: string) {
    this.graph.removeNode(id);
  }

  public removeEdge(id: string) {
    this.graph.removeEdge(id);
  }

  public layout() {
    this.GT_cmd.layoutClickHandle();
  }

  public setShowStencli(p: boolean) {
    if (Tsrv.getx(this.config, 'stencil.show', true)) {
      this.showStencil = p;
    }
  }

  public setShowToolbar(p: boolean) {
    this.showToolbar = p;
  }

  public setShowMinimap(p: boolean) {
    this.showMinimap = p;
  }

  public reLoadDataFromJson(json: string) {
    this.GT_cmd.reLoadDataFromJson(json);
  }

  public isDenyEdit() {
    return this.GT_cmd.isDenyEdit();
  }
}
</script>
<style lang="scss" scoped>
.main {
  width: 100%;
  height: 100%;
  padding: 0px;
  display: flex;
  flex-direction: column;
  .top {
    height: 40px;
  }
  .body {
    flex-grow: 1;
    display: flex;
    .left {
      background: #fff;
      position: relative;
      width: 200px;
      overflow: auto;
    }
    .garaph-container {
      flex: 1;
    }
    .minmap {
      border: 1px solid #a39b9b;
      position: absolute;
      width: 310px;
      height: 210px;
      right: 0;
      bottom: 0;
    }
  }
}

#mountNode,
.editor {
  height: 100%;
  width: 100%;
  padding: 0;
  margin: 0;
  position: relative;
  &.fullScreen {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    z-index: 9999;
    background-color: #fff;
  }
}
</style>
